package org.bim.view.drag.design.config;



import com.github.xiaoymin.knife4j.core.extend.OpenApiExtendSetting;
import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import com.github.xiaoymin.knife4j.spring.filter.ProductionSecurityFilter;
import com.google.common.base.Strings;
import org.apache.commons.codec.binary.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;


/**
 * swagger配置
 *
 * @author Chill
 */
@Configuration
@EnableSwagger2WebMvc
@ConditionalOnProperty(name = {"swagger.enable"}, havingValue = "true")
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerAutoConfiguration {

    private static final String BASE_PATH = "/**";
    private static final String SPLITOR = ",";
    private static final String BASE_PACKAGE = "org.bim.view";

    @Bean
    @ConditionalOnMissingBean(Knife4jProperties.class)
    public Knife4jProperties knife4jProperties() {
        Knife4jProperties properties = new Knife4jProperties();
        properties.setEnable(true);
        return properties;
    }

    @Bean(initMethod = "start")
    @ConditionalOnMissingBean(OpenApiExtensionResolver.class)
    public OpenApiExtensionResolver openApiExtensionResolver(Knife4jProperties knife4jProperties) {
        OpenApiExtendSetting setting = knife4jProperties.getSetting();
        if (setting == null) {
            setting = new OpenApiExtendSetting();
        }

        return new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments());
    }

    /**
     * swagger不允许时，增加这个拦截器,如果未设置，也要有这个过滤器
     *
     * @return 拦截器
     */
    @Bean
    @ConditionalOnProperty(name = {"swagger.enable"}, havingValue = "false",matchIfMissing = true)
    public ProductionSecurityFilter swaggerSecurityFilter() {
        return new ProductionSecurityFilter(true);
    }

    @Bean
    public Docket api(SwaggerProperties swaggerProperties) {
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        List<Parameter> globalParams = globalParams(swaggerProperties.getHeaderTokenKeys(), swaggerProperties.getQueryTokenKeys());

        return new Docket(DocumentationType.SWAGGER_2)
                .directModelSubstitute(LocalDateTime.class,String.class)
                .directModelSubstitute(LocalDate.class, String.class)
                .directModelSubstitute(LocalTime.class, String.class)
                .directModelSubstitute(ZonedDateTime.class,String.class)
                .enable(true)
                .host(swaggerProperties.getHost())
                .apiInfo(apiInfo(swaggerProperties)).select()
                .apis(basePackage(String.join(SPLITOR,swaggerProperties.getBasePackage())))
                .paths(PathSelectors.any())

                .build()
                .globalOperationParameters(globalParams);
    }

    private List<Parameter> globalParams(Set<String> headers, Set<String> params) {
        List<Parameter> pars = new ArrayList<>();
        /*添加接口请求头参数配置 没有的话 可以忽略*/
        headers.forEach(header -> {
            ParameterBuilder tokenPar = new ParameterBuilder();
            tokenPar.name(header).description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
            pars.add(tokenPar.build());
        });
        params.forEach(param -> {
            ParameterBuilder tokenPar = new ParameterBuilder();
            tokenPar.name(param).description("令牌").modelRef(new ModelRef("string")).parameterType("query").required(false).build();
            pars.add(tokenPar.build());
        });
        return pars;

    }


    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }

    private static Predicate<RequestHandler> basePackage(String basePackage) {
        if (!BASE_PACKAGE.equals(basePackage)) {
            basePackage = BASE_PACKAGE + SPLITOR + basePackage;
        }
        String finalBasePackage = basePackage;
        return input -> declaringClass(input).map(handlerPackage(finalBasePackage)).orElse(true);
    }

    private static Function<Class<?>, Boolean> handlerPackage(String basePackage) {
        return input -> {
            // 循环判断匹配
            for (String strPackage : basePackage.split(SPLITOR)) {
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.ofNullable(input.declaringClass());
    }

}
